package com.stars.easyms.rest.handler;

import com.stars.easyms.base.http.EasyMsRequestEntity;
import com.stars.easyms.base.http.EasyMsResponseEntity;
import com.stars.easyms.monitor.handler.EasyMsRequestHandler;
import com.stars.easyms.rest.RestResponseHandler;
import com.stars.easyms.rest.RestService;
import com.stars.easyms.rest.dto.EasyMsRestRequestEntity;
import com.stars.easyms.rest.function.EasyMsRestFunction;
import com.stars.easyms.rest.initializer.RequestMappingPathForRestInfo;
import com.stars.easyms.rest.bean.RestLockInfo;
import com.stars.easyms.rest.constant.RestConstants;
import com.stars.easyms.rest.bean.RestInfo;
import com.stars.easyms.rest.lock.EasyMsRestLock;
import com.stars.easyms.rest.properties.EasyMsRestProperties;
import com.stars.easyms.base.util.ApplicationContextHolder;
import org.springframework.http.*;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import java.util.Set;

/**
 * <p>className: EasyMsRestRequestHandler</p>
 * <p>description: Rest请求处理类</p>
 *
 * @author guoguifang
 * @version 1.2.1
 * @date 2019/7/11 14:32
 */
public final class EasyMsRestRequestHandler implements EasyMsRequestHandler {

    private final RestResponseHandler restResponseHandler;

    private final EasyMsRestExceptionHandler easyMsRestExceptionHandler;

    private final EasyMsRestMessageHandler easyMsRestMessageHandler;

    private final EasyMsResponseHandler easyMsResponseHandler;

    private final Validator validator;

    public boolean filter(HttpServletRequest request, HttpServletResponse response,
                          @Nullable EasyMsRequestEntity requestEntity, EasyMsRestFunction easyMsRestFunction) {

        // 为了数据安全性和接口统一性，rest接口只接受post请求，其他请求不做rest处理
        if (requestEntity != null && HttpMethod.POST.matches(request.getMethod())) {

            // 获取请求地址，根据请求地址获取rest信息，如果找不到对应rest信息也不做rest处理
            String requestPath = requestEntity.getRequestPath();
            RequestMappingPathForRestInfo requestMappingPathForRestInfo = RestConstants.REQUEST_MAPPING_PATH_MAP.get(requestPath);
            if (requestMappingPathForRestInfo != null) {

                // 将HttpServletRequest、HttpServletResponse放入线程本地变量中
                EasyMsRestRequestEntity restRequestEntity = new EasyMsRestRequestEntity(requestEntity);
                EasyMsRestSynchronizationManager.setRequestAndResponse(request, response, restRequestEntity);

                // 解析请求体并执行请求服务，此处会抛出异常
                EasyMsResponseEntity restResponse;
                Object encryptedResponseEntity = null;
                try {

                    // 获取请求数据
                    easyMsRestMessageHandler.parseRequestBody(request, restRequestEntity, requestMappingPathForRestInfo);

                    // 执行请求并获取返回值，默认无事务状态运行，若需事务需自行增加事务注解
                    restResponse = easyMsRestFunction.execute(restRequestEntity);

                    // 如果需要加密则对ResponseEntity进行加密
                    encryptedResponseEntity = easyMsResponseHandler.encryptResponseEntity(restResponse, requestMappingPathForRestInfo);
                } catch (Throwable t) {

                    // 处理异常的警告信息
                    easyMsRestExceptionHandler.handleRestAlarmException(t);

                    // 请求处理异常后封装返回信息
                    restResponse = restResponseHandler.handleRestResponseWithException(t);
                }

                // 处理返回值，此处可能会抛出异常
                try {

                    easyMsResponseHandler.handleResponse(restRequestEntity, restResponse, encryptedResponseEntity, response);
                } catch (Throwable t) {

                    // 处理异常的警告信息
                    easyMsRestExceptionHandler.handleRestAlarmException(t);
                } finally {

                    // 清除线程本地变量
                    EasyMsRestSynchronizationManager.clear();
                }
                return true;
            }
        }
        return false;
    }

    /**
     * 处理接口请求
     */
    @NonNull
    @SuppressWarnings("unchecked")
    public EasyMsResponseEntity doExecute(@NonNull EasyMsRestRequestEntity restRequestEntity) throws Exception {

        RestInfo restInfo = restRequestEntity.getRestInfo();
        try {
            // 获取restService对象
            RestService restServiceInstance = restInfo.getRestServiceInstance();

            // 报文体数据验证
            validateWithException(restRequestEntity.getBody());

            // 获取锁信息，如果不为空则默认开启锁，如果加锁失败则返回异常信息，这里使用的是阻塞锁若超过配置的超时时间后会返回超时异常
            // rest分布式锁：用于需要在rest接口层面加锁的场景，例如同一个接口同一个用户同一时间只允许进行一次操作的场景或者其他类似场景
            RestLockInfo restLockInfo = restServiceInstance.getRestLockInfo(restInfo, restRequestEntity.getBody());
            EasyMsRestLock.tryRestLock(restLockInfo);

            // 执行请求并对output进行转换
            Object output = restServiceInstance.execute(restRequestEntity.getBody());

            // 请求处理成功后封装返回信息
            return restResponseHandler.handleRestResponse(output);
        } finally {

            // 释放rest锁资源
            EasyMsRestLock.releaseRestLock();
        }
    }

    /**
     * 按照字段注解校验输入字段
     */
    private <T> void validateWithException(@Nullable T body) {
        if (validator != null && body != null) {
            Set<ConstraintViolation<T>> constraintViolations = validator.validate(body);
            if (!constraintViolations.isEmpty()) {
                throw new ConstraintViolationException(constraintViolations);
            }
        }
    }

    public EasyMsRestRequestHandler(EasyMsRestProperties easyMsRestProperties, EasyMsResponseHandler easyMsResponseHandler) {
        RestResponseHandler localRestResponseHandler = ApplicationContextHolder.getBean(RestResponseHandler.class);
        this.restResponseHandler = localRestResponseHandler != null ? localRestResponseHandler : new EasyMsRestResponseHandler();
        this.easyMsRestExceptionHandler = new EasyMsRestExceptionHandler();
        this.validator = ApplicationContextHolder.getApplicationContext().getBean(Validator.class);
        this.easyMsRestMessageHandler = new EasyMsRestMessageHandler(easyMsRestProperties);
        this.easyMsResponseHandler = easyMsResponseHandler;
    }
}
